/////////////////////////////////////////////////////////////////////////////
// Original code from libhdfs3. Copyright (c) 2013 - 2014, Pivotal Inc.
// All rights reserved. Author: Zhanwei Wang
/////////////////////////////////////////////////////////////////////////////
//  Modifications by Kumo Inc.
// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//



#pragma once

#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <string>
#include <sstream>
#include <functional>
#include <turbo/base/macros.h>
#include <kmhdfs/common/stack_printer.h>

#define STACK_DEPTH 64

#define PATH_SEPRATOR '/'

inline static const char *SkipPathPrefix(const char *path) {
    int i, len = strlen(path);

    for (i = len - 1; i > 0; --i) {
        if (path[i] == PATH_SEPRATOR) {
            break;
        }
    }

    assert(i > 0 && i < len);
    return path + i + 1;
}

#include <exception>
#include <stdexcept>

namespace Hdfs {
    using std::rethrow_exception;
    using std::current_exception;
    using std::make_exception_ptr;
    using std::exception_ptr;
}

namespace Hdfs {
    using std::throw_with_nested;
    using std::rethrow_if_nested;
} //  namespace Hdfs


namespace Hdfs {
    namespace Internal {
        template<typename THROWABLE>
        TURBO_NORETURN TURBO_NOINLINE

        void ThrowException(bool nested, bool printStack, const char *f, int l,
                            const char *exceptionName, const char *fmt, ...) __attribute__((format(printf, 6, 7)));

        template<typename THROWABLE>
        TURBO_NORETURN TURBO_NOINLINE

        void ThrowException(bool nested, bool printStack, const char *f, int l,
                            const char *exceptionName, const char *fmt, ...) {
            va_list ap;
            va_start(ap, fmt);
            std::string buffer;
            buffer = exceptionName;
            buffer.append(": ");
            int size = vsnprintf(NULL, 0, fmt, ap);
            va_end(ap);
            int offset = buffer.size();
            buffer.resize(offset + size + 1);
            va_start(ap, fmt);
            vsnprintf(buffer.data() + offset, size + 1, fmt, ap);
            va_end(ap);

            if (!nested) {
                throw THROWABLE(buffer.c_str(), SkipPathPrefix(f), l,
                                printStack ? Hdfs::Internal::print_stack(1, STACK_DEPTH).c_str() : "");
            } else {
                Hdfs::throw_with_nested(
                    THROWABLE(buffer.c_str(), SkipPathPrefix(f), l,
                              printStack ? Hdfs::Internal::print_stack(1, STACK_DEPTH).c_str() : ""));
            }

            throw std::logic_error("should not reach here.");
        }
    } //  namespace Internal
} //  namespace Hdfs


namespace Hdfs {
    /**
     * A user defined callback function used to check if a slow operation has been canceled by the user.
     * If this function return true, HdfsCanceled will be thrown.
     */
    extern std::function<bool(void)> ChecnOperationCanceledCallback;

    class HdfsException;
}

namespace Hdfs {
    namespace Internal {
        /**
         * Check if a slow operation has been canceled by the user.
         * @throw return false if operation is not canceled, else throw HdfsCanceled.
         * @throw HdfsCanceled
         */
        bool CheckOperationCanceled();

        /**
         * Get a exception's detail message.
         * If the exception contains a nested exception, recursively get all the nested exception's detail message.
         * @param e The exception which detail message to be return.
         * @return The exception's detail message.
         */
        const char *GetExceptionDetail(const Hdfs::HdfsException &e,
                                       std::string &buffer);

        /**
         * Get a exception's detail message.
         * If the exception contains a nested exception, recursively get all the nested exception's detail message.
         * @param e The exception which detail message to be return.
         * @return The exception's detail message.
         */
        const char *GetExceptionDetail(const exception_ptr e, std::string &buffer);

        const char *GetExceptionMessage(const exception_ptr e, std::string &buffer);

        /**
         * Get a error information by the given system error number.
         * @param eno System error number.
         * @return The error information.
         * @throw nothrow
         */
        const char *GetSystemErrorInfo(int eno);
    }
}

#define THROW(throwable, fmt, ...) \
    Hdfs::Internal::ThrowException<throwable>(false, true, __FILE__, __LINE__, #throwable, fmt, ##__VA_ARGS__);

#define NESTED_THROW(throwable, fmt, ...) \
    Hdfs::Internal::ThrowException<throwable>(true, true, __FILE__, __LINE__, #throwable, fmt, ##__VA_ARGS__);

#define THROW_NO_STACK(throwable, fmt, ...) \
    Hdfs::Internal::ThrowException<throwable>(false, false, __FILE__, __LINE__, #throwable, fmt, ##__VA_ARGS__);
